/**
 * Copyright (C) 2017 - 2020 https://github.com/joewee
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bifrost.core;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.bifrost.exception.ApiException;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 *处理api请求
 *
 *@author joewee
 *@version 1.0.0
 *@Date 2018/2/2 16:18
 */
@Component
public class ApiGateWayHandler implements ApplicationContextAware {
private static final Logger LOGGER = LoggerFactory.getLogger(ApiGateWayHandler.class.getName());

    @Autowired
    ParameterNameDiscoverer parameterNameDiscoverer;

    private static final String METHOD = "method";
    public static final String PARAMTERS = "paramters";
    public static final String CONTENT_TYPE = "application/json";
    private static ApiStore apiStore;
    @Bean
    @ConditionalOnMissingBean
    public ParameterNameDiscoverer getParameterNameDiscoverer(){
        return new LocalVariableTableParameterNameDiscoverer();
    }
    public void handle(HttpServletRequest request, HttpServletResponse response) {
        String method = request.getParameter(METHOD);
        String parameter = request.getParameter(PARAMTERS);
        ApiStore.ApiRunnable apiRunable = null;
        Object result =null;
        try {
            apiRunable = parametersValidate(request);
            Object[] args =buildParameters(apiRunable,parameter,request,response);
            result= apiRunable.run(args);
        } catch (ApiException e) {
            response.setStatus(500);
            LOGGER.error(String.format("调用接口：%s异常，参数：%s",method,PARAMTERS),e);
            result = handleError(e);
        } catch (InvocationTargetException e) {
            response.setStatus(500);
            LOGGER.error(String.format("调用接口：%s异常，参数：%s",method,PARAMTERS),e.getTargetException());
            result = handleError(e.getTargetException());
        }catch (Exception e) {
            response.setStatus(500);
            LOGGER.error(String.format("调用接口：%s异常，参数：%s",method,PARAMTERS),e);
            result = handleError(e);
        }
        responseBuiler(response, CONTENT_TYPE, result);
    }

    private Object handleError(Throwable e) {
        return  "";
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        apiStore = new ApiStore(applicationContext);
    }

    public void responseBuiler(HttpServletResponse response, String contentType, Object result) {
        try {
            String type = contentType == null || contentType == "" ? CONTENT_TYPE : contentType;
            response.setContentType(type);
            response.setCharacterEncoding("UTF-8");
            response.setHeader("Cache-Control" ,"no-cache");
            response.setHeader("Pragma","no-cache");
            response.setDateHeader("Expires",0);
            if (result instanceof String) {
                response.getWriter().write(result.toString());
            } else {
                response.getWriter().write(JSON.toJSONString(result));
            }
        } catch (IOException e) {
            LOGGER.error("服务中心响应异常",e);
            throw  new RuntimeException(e);
        }
    }

    private ApiStore.ApiRunnable parametersValidate(HttpServletRequest request) throws ApiException{
        String apiName = request.getParameter(METHOD);
        String json = request.getParameter(PARAMTERS);
        ApiStore.ApiRunnable api ;
        if(StringUtils.isEmpty(apiName)){
            throw new ApiException("调用失败，参数'method' 为空");
        }else if(StringUtils.isEmpty(json)){
            throw new ApiException("调用失败，参数'params'为空");
        }else if((api = apiStore.findApiRunable(apiName))==null){
            throw new ApiException("调用失败，指定API不存在，API:"+apiName);
        }
        //TODO 签名验证
        return  api;
    }
    private Object[] buildParameters(ApiStore.ApiRunnable runnable, String json, HttpServletRequest request, HttpServletResponse response) throws ApiException {
        Map<String,Object> map =null;
        try {
            map = JSON.parseObject(json, new TypeReference<Map<String, Object>>(){});
        } catch (Exception e) {
            throw new ApiException("调用失败：json格式异常，请检查参数"+PARAMTERS);
        }
        if(map == null){
            map =new HashMap<>();
        }
        Method method = runnable.getTargetMethod();

        List<String> paramNames = new ArrayList<>();
        String [] s = parameterNameDiscoverer.getParameterNames(method);
        Collections.addAll(paramNames, parameterNameDiscoverer.getParameterNames(method));
        Class<?> [] paramTypes = method.getParameterTypes();
        for(Map.Entry<String,Object> m : map.entrySet()){
            if(!paramNames.contains(m.getKey())){
                throw new ApiException(String.format("调用失败：接口不存在'%s'参数",m.getKey()));
            }
        }
        Object [] args = new Object[paramTypes.length];
        for(int i = 0;i<paramTypes.length;i++){
            if(paramTypes[i].isAssignableFrom(HttpServletRequest.class)){
                args[i]=request;
            }else if(map.containsKey(paramNames.get(i))){
                try {
                    args[i] = convertJsonToBean(map.get(paramNames.get(i)),paramTypes[i]);
                } catch (Exception e) {
                   throw new ApiException(String.format("调用失败：指定参数格式错误或值错误%s-%s",paramNames.get(i),e.getMessage()));
                }
            }else{
                args[i] = null;
            }
        }
        return args;
    }

    /**
     * 将json参数转换为对应的bean
     * @param object
     * @param targetClass
     * @param <T>
     * @return
     * @throws Exception
     */
    private <T> Object convertJsonToBean(Object object,Class<T> targetClass) throws Exception{
        Object result =null;
        if(object == null){
            return null;
        }else if(Integer.class.equals(targetClass)){
            result = Integer.parseInt(object.toString());
        }else if(Long.class.equals(targetClass)){
            result = Long.parseLong(object.toString());
        }else if(Date.class.equals(targetClass)){
            if(object.toString().matches("[0-9]+")){
                result = new Date(Long.parseLong(object.toString()));
            }else{
                throw new IllegalArgumentException("日期必须是长整型的时间戳");
            }
        }else if(String.class.equals(targetClass)){
            if(object instanceof String){
                result =object;
            }else{
                throw new IllegalArgumentException("转换模板类型不为字符串");
            }
        }else{
            result =  JSON.parseObject(object.toString(),targetClass);
        }
        return  result;
    }
}
